﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;

namespace Generator
{
    //[Serializable()]
    //[XmlRoot("GeneratedRatings", IsNullable = false)]
    //public class Ratings
    //{
    //    [XmlArray("Rating")]
    //    public int[] SavedRatings;
    //}

    public class Generator
    {
        private int[] ratings;
        private int[,] data;
        private int usersN = 0;
        private int moviesN = 0;
        private const int correlationConst = 40;
        private string path = String.Empty;
        private byte averageMark = 3;

        public Generator() { }

        /// <summary>
        /// Конструктор
        /// </summary>
        /// <param name="data">Данные</param>
        /// <param name="usersN">Количество пользователей</param>
        /// <param name="moviesN">Количество фильмов</param>
        public Generator(int[,] data, int usersN, int moviesN, string path)
        {
            this.data = data;
            this.usersN = usersN + 1;
            this.moviesN = moviesN + 1;
           // ratings = new int[this.usersN * this.moviesN + 1];
            this.path = path;
        }

        /// <summary>
        /// Генерирует рейтинги
        /// </summary>
        public void Generate()
        {
            int[] averageMarks = GetAverageMarks(moviesN);
            int[] moviesU;
            List<int> correlatingUsers = new List<int>();
            List<int> localCorrelatingUsers = new List<int>();
            float numerator = 0;
            float denominator = 0;
            float weight = 0;
            FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);

            for (int userIndex = 0; userIndex < usersN; userIndex++)
            {
                moviesU = GetMovieU(userIndex);

                //if (moviesU.Length < 1)
                //{
                //    continue;
                //}

                for (int movieIndex = 0; movieIndex < moviesN; movieIndex++)
                {
                    if (moviesU.Length < 1)
                    {
                        fs.Write(new byte[] { averageMark }, 0, 1);
                        continue;
                    }

                    if (data[userIndex, movieIndex] > 0)
                    {
                        fs.Write(new byte[] { (byte)data[userIndex, movieIndex] }, 0, 1);
                        continue;
                    }

                    numerator = 0;
                    denominator = 0;
                    weight = 0;

                    correlatingUsers = GetCorrelatingUsers(movieIndex, moviesU);

                    if (correlatingUsers.Count < 1)
                    {
                        fs.Write(new byte[] { averageMark }, 0, 1);
                        continue;
                    }

                    foreach (int otherMovieIndex in moviesU.Where(otherMovieIndex => movieIndex != otherMovieIndex))
                    {
                        localCorrelatingUsers = correlatingUsers.Where(user => data[user, movieIndex] > 0 && data[user, otherMovieIndex] > 0).ToList<int>();

                        if (localCorrelatingUsers.Count() < 1)
                        {
                            continue;
                        }

                        weight = GetWeight(movieIndex, otherMovieIndex, localCorrelatingUsers);
                        numerator += weight * (data[userIndex, otherMovieIndex] - averageMarks[otherMovieIndex]);
                        denominator += Math.Abs(weight);
                    }

                   // ratings[userIndex * moviesN + movieIndex] = averageMarks[movieIndex] + (int)Math.Ceiling(numerator / denominator);

                    fs.Write(new byte[] { (byte)(averageMarks[movieIndex] + (int)Math.Ceiling(numerator / denominator)) }, 0, 1);
                }
            }

            fs.Close();
        }

        /// <summary>
        /// Вычисляет средние оценки фильмов
        /// </summary>
        /// <returns>Средние оценки</returns>
        private int[] GetAverageMarks(int moviesN)
        {
            float[] averageMark = new float[moviesN];
            int[] number = new int[moviesN];
            int[] result = new int[moviesN];

            for (int movieIndex = 0; movieIndex < moviesN; movieIndex++)
            {
                for (int userIndex = 0; userIndex < usersN; userIndex++)
                {
                    if (data[userIndex, movieIndex] != -1)
                    {
                        averageMark[movieIndex] += data[userIndex, movieIndex];
                        number[movieIndex]++;
                    }
                }
            }

            for (int movieIndex = 0; movieIndex < moviesN; movieIndex++)
            {
                if (number[movieIndex] != 0)
                {
                    averageMark[movieIndex] = averageMark[movieIndex] / number[movieIndex];
                }

                result[movieIndex] = (int)Math.Ceiling(averageMark[movieIndex]);
            }

            return result;
        }

        /// <summary>
        /// Вычисляется множество фильмов, которые пользователь оценил
        /// </summary>
        /// <param name="user">Пользователь</param>
        /// <returns>Список фильмов</returns>
        private int[] GetMovieU(int user)
        {
            List<int> result = new List<int>();

            for (int movieIndex = 0; movieIndex < moviesN; movieIndex++)
            {
                if (data[user, movieIndex] > 0)
                {
                    result.Add(movieIndex);
                }
            }

            return result.ToArray<int>();
        }

        /// <summary>
        /// Вычисляет вес
        /// </summary>
        /// <param name="i">Номер фильма i</param>
        /// <param name="j">Номер фильма j</param>
        /// <returns>Вес для i,j</returns>
        private float GetWeight(int iMovie, int jMovie, List<int> correlatingUsers)
        {
            int numerator = 0;
            int denominator = 0;
            int product = 0;

            foreach (int user in correlatingUsers)
            {
                product = data[user, iMovie] * data[user, jMovie];
                numerator += product;
                denominator += product * product;
            }

            return (float)(numerator / Math.Sqrt(denominator));
        }

        /// <summary>
        /// Выборка пользователей оценивших вместе не менее n фильмов
        /// </summary>
        /// <param name="i">Номер фильма i</param>
        /// <param name="j">Номер фильма j</param>
        /// <returns>Коррелирующие пользователи</returns>
        private List<int> GetCorrelatingUsers(int movie, int[] otherMovies)
        {
            List<int> users = new List<int>();

            for (int userIndex = 0; userIndex < usersN; userIndex++)
            {
                if (data[userIndex, movie] > 0 && IsCorrelationEnough(userIndex, otherMovies))
                {
                    users.Add(userIndex);
                }
            }

            return users;
        }

        /// <summary>
        /// Вычисляет сколько фильмов среди movies оценил пользователь userIndex
        /// И достаточно ли такого количества
        /// </summary>
        /// <param name="userIndex">Пользователь</param>
        /// <param name="movies">Фильмы</param>
        /// <returns></returns>
        private bool IsCorrelationEnough(int userIndex, int[] movies)
        {
            int result = 0;

            foreach (int movie in movies)
            {
                if (data[userIndex, movie] > 0)
                {
                    if (++result >= correlationConst)
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        //private void SaveRatings()
        //{
        //    Ratings toSave = new Ratings();
        //    toSave.SavedRatings = ratings;

        //    XmlSerializer serializer = new XmlSerializer(typeof(Ratings));
        //    Stream fs = new FileStream(path, FileMode.Create);
        //    XmlWriter writer = new XmlTextWriter(fs, Encoding.Unicode);

        //    serializer.Serialize(writer, toSave);
        //    writer.Close();
        //}
    }
}

